#include "../include/VideoManager.h"
#include "../include/Window.h"
#include "../include/Utility.h"
#include <SDL2/SDL.h>
#include <utility>
#include <stdexcept>
#include <string>

#include <assert.h>

#include <new>
#include "../include/angelscript.h"
#include "../anglescript/scriptstdstring/scriptstdstring.h"
#include "../anglescript/scriptbuilder/scriptbuilder.h"
#include "../anglescript/autowrapper/aswrappedcall.h"


namespace GUL {
    namespace System {
        namespace Graphic {
            
            VideoManager* FactoryVideoManager()
            {
                return new VideoManager;
            }
            
            void VideoManager::AddScriptEngine(asIScriptEngine* engine)
            {
                int r = 0;
                r = engine->RegisterObjectType("VideoManager",0,asOBJ_REF );
                r = engine->RegisterObjectBehaviour("VideoManager",asBEHAVE_FACTORY,"VideoManager @f()",asFUNCTION(FactoryVideoManager),asCALL_CDECL) ;assert ( r >= 0 );
                r = engine->RegisterObjectBehaviour("VideoManager",asBEHAVE_ADDREF,"void f()",asMETHOD(VideoManager,AddRef),asCALL_THISCALL); assert ( r >= 0);
                r = engine->RegisterObjectBehaviour("VideoManager",asBEHAVE_RELEASE,"void f()",asMETHOD(VideoManager,ReleaseRef),asCALL_THISCALL); assert ( r >= 0);
                r = engine->RegisterObjectMethod("VideoManager","void Create(string name, int x,int y,int w,int h,int flag)",asMETHOD(System::Graphic::VideoManager,Create),asCALL_THISCALL); assert (r >= 0);
                r = engine->RegisterObjectMethod("VideoManager","Window@ at(string)",asMETHOD(VideoManager,at),asCALL_THISCALL); assert (r >= 0);

            }
            
            void VideoManagerRelease(VideoManager* obj)
            {
                if( obj )
                    delete obj;
            }

            VideoManager::VideoManager(const std::string& Name, UPair Pos, UPair Boundary, Uint32 flags) {
                this->m_SDLinit.InitVideoSupport();
                this->m_SDLinit.InitTTFSupport();
                this->Create1(Name, Pos, Boundary, flags);
                this->m_referenceCount = 1;
            }

            VideoManager::VideoManager() {
                this->m_SDLinit.InitVideoSupport();
                                this->m_referenceCount = 1;

            }

            VideoManager::~VideoManager() {
                for (WindowIter iter = this->m_WindowDictionary.begin(); iter != this->m_WindowDictionary.end(); iter++) {
                    iter->second->~Window();
                }
                this->shutdown();
                this->m_SDLinit.QuitTTFSupport();
            }

            Window& VideoManager::at(const std::string& name) {
                return this->at(name.c_str());
            }

//            Window& VideoManager::at(const char* name) {
//                return *this->m_WindowDictionary[std::string(name)];
//            }

//            std::string VideoManager::at(int index) {
//                return std::string(SDL_GetVideoDriver(index));
//            }

            void VideoManager::Create1(const std::string& Name, UPair Pos, UPair Boundary, Uint32 flags) {
                Window* NewWindow = new Window(Name, Pos, Boundary, flags);
                if (NewWindow == nullptr)
                    throw std::runtime_error("Could instantiate a new Window. I suggest you check your memory, perhaps you has not much left ^-^!\n");
                this->m_WindowDictionary.insert(std::make_pair(Name, NewWindow));
            }
                       
            void VideoManager::Create(const std::string& Name, int x, int y, int w, int h, Uint32 flags)
            {
                Window* NewWindow = new Window(Name, IPair(x,y), IPair(w,h), flags);
                if (NewWindow == nullptr)
                    throw std::runtime_error("Could instantiate a new Window. I suggest you check your memory, perhaps you has not much left ^-^!\n");
                this->m_WindowDictionary.insert(std::make_pair(Name, NewWindow));
            }


            void VideoManager::Destroy(const std::string& Name) {
                this->operator [](Name).~Window();
            }

            Window& VideoManager::operator [](const std::string& name) {
                if (this->m_WindowDictionary.find(name) == this->m_WindowDictionary.end())
                    throw std::out_of_range("It doesn't exist a Window with name: " + name + " here. sorry out of range");
                return *this->m_WindowDictionary.at(name);
            }

            int VideoManager::DisplayBounds(int display, SDL_Rect* rect) {
                if (display == -1 || rect == NULL)
                    throw std::invalid_argument("Displaybounds accept only positiv display indices and no nullptr of SDL_Rect\n");
                return SDL_GetDisplayBounds(display, rect);
            }

            int VideoManager::NumVideoDrivers() {
                return SDL_GetNumVideoDrivers();
            }

            int VideoManager::NumVideoDisplays() {
                return SDL_GetNumVideoDisplays();
            }

            std::string VideoManager::VideoDriver(int index) {
                if (index < -1)
                    throw std::invalid_argument("VideoDriver accept only indices greater than -1\n");
                return SDL_GetVideoDriver(index);
            }

            std::string VideoManager::CurrentVideoDriver() {
                return SDL_GetCurrentVideoDriver();
            }

            void VideoManager::shutdown() {
                if (this->m_WindowDictionary.size() == 0)
                    throw std::runtime_error("You dumbass, everything it's already down");
                SDL_VideoQuit();
            }

            void VideoManager::startup(const std::string& drivername) {
                SDL_VideoInit(drivername.c_str());
            }

            void VideoManager::GL_LoadLibrary(const std::string& path) const {
                SDL_GL_LoadLibrary(path.c_str());
            }

            void* VideoManager::GL_GetProcAddress(const std::string& function) const {
                return SDL_GL_GetProcAddress(function.c_str());
            }

            void VideoManager::GL_UnloadLibrary() const {
                SDL_GL_UnloadLibrary();
            }

            bool VideoManager::GL_ExtensionSupported(const std::string& ext) const {
                return SDL_GL_ExtensionSupported(ext.c_str());
            }

            int VideoManager::GL_SetAttribute(SDL_GLattr attr, int val) const {
                return SDL_GL_SetAttribute(attr, val);
            }

            int VideoManager::GL_GetAttribute(SDL_GLattr attr, int* val) const {
                return SDL_GL_GetAttribute(attr, val);
            }

            int VideoManager::GL_MakeCurrent(const std::string& Windowname, SDL_GLContext cont) {
                return SDL_GL_MakeCurrent((*this)[Windowname], cont);
            }

            Window& VideoManager::GL_GetCurrentWindow() {
                return this->at(SDL_GetWindowTitle(SDL_GL_GetCurrentWindow()));
            }

            SDL_GLContext VideoManager::GL_GetCurrentContext() const {
                return SDL_GL_GetCurrentContext();
            }

            void VideoManager::GL_GetDrawableSize(const std::string& Windowname, int* w, int* h) {
                if (w != nullptr && h != nullptr)
                    throw std::invalid_argument("VideoManager::GL_GetDrawableSize(): Please use with nullptr initialized pointers of type int for the arguments 'w' and 'h' !!!\n");
                SDL_GL_GetDrawableSize((*this)[Windowname], w, h);
            }

            void VideoManager::GL_SetSwapInterval(int interval) const {
                SDL_GL_SetSwapInterval(interval);
            }

            int VideoManager::GL_GetSwapInterval() const {
                return SDL_GL_GetSwapInterval();
            }

            void VideoManager::GL_SwapWindow(const std::string& Windowname) {
                SDL_GL_SwapWindow((*this)[Windowname]);
            }

            void VideoManager::GL_DeleteContext(SDL_GLContext context) {
                SDL_GL_DeleteContext(context);
            }

            SDL_GLContext VideoManager::GL_CreateContext(const std::string& window) {
                return SDL_GL_CreateContext(this->m_WindowDictionary[window]->operator SDL_Window * ());
            }

        }

    }

}